package com.wanghua.spring.interceptor.annotation;

import com.wanghua.spring.bean.entity.VisitDetail;
import com.wanghua.spring.service.AopService;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

//切面bean（Spring）
@Component
@Aspect
public class AopServiceAspect {

    //配置切入点，该方法无方法体，只要是为了方便此类中的其他方法使用此切入点配置。
    //@Pointcut("execution(* com.wanghua.spring.service..*(..))")   //包下的任意连接点
    @Pointcut("execution(* com.wanghua.spring.service..*.*(..))") //包下的任意方法
    public void aspect() {
    }

    /*
	 * 配置前置通知,使用在方法aspect()上注册的切入点
	 * 同时接受JoinPoint切入点对象,可以没有该参数
	 *
	 * 这里使用了"args(type,visitDetail)"，使得只有带有两个参数的"withAop1"方法才会使用此通知。一个参数此通知不会被调用的。
	 * 此 before 方法的第二个、第三个参数可以直接用的，会传过来值的。当然我们也可以通过连接点的“joinPoint.getArgs()”来获取。
	 */
    @Before("aspect()&&args(type,visitDetail)")
    public void before(JoinPoint joinPoint, String type, VisitDetail visitDetail) throws Exception {
        //JoinPoint提供了一系列有用的方法：
        Object[] _args = joinPoint.getArgs();                      //返回方法参数（如果有两个重载方法，只是参数不同，就可以用这里的参数识别了）
        Object _this = joinPoint.getThis();                        //返回代理对象
        AopService _target = (AopService) joinPoint.getTarget();   //返回目标
        Signature _signature = joinPoint.getSignature();           //返回正在被通知的方法相关信息
        String service = _signature.getDeclaringTypeName();  //值：com.wanghua.spring.service.AopService
        String method = _signature.getName();                //值：withAop1
        String _str = joinPoint.toString();   //打印出正在被通知的方法的有用信息
        System.out.println("before " + joinPoint);
    }

    //配置后置通知,使用在方法aspect()上注册的切入点
    @After("aspect()")
    public void after(JoinPoint joinPoint) {
        System.out.println("after " + joinPoint);
    }

    //Around 增强处理近似等于 Before 增强处理和  AfterReturning 增强处理的总和。它可改变执行目标方法的参数值，也可改变目标方法之后的返回值。
    //这里必须有返回值，否则 @AfterReturning 中获取不到返回值。
    //@Around(value = "execution(public void com.SpringAOP.HelloWord.HelloWord.sayHello(..))")
    @Around("aspect()")
    public Object around(ProceedingJoinPoint joinPoint) {
        long start = System.currentTimeMillis();
        try {
            /*必须调用目标方法，如果不调用，目标方法将不被执行。比如当调用"withAop1()"方法时，会先执行 @Around 通知，此通知上调用 "proceed()"
              后才会执行 @Before 通知，接着执行 "withAop1()"方法，再接着是 @Around 通知 "proceed()" 后的代码继续被执行，接着是 @After 通知，
              最后是 @AfterReturning 通知
            */
            Object returnVal = joinPoint.proceed();  //"withAop1()"方法返回值
            long end = System.currentTimeMillis();
            System.out.println("around " + joinPoint + "\tUse time : " + (end - start) + " ms!");
            return returnVal;
        } catch (Throwable e) {
            long end = System.currentTimeMillis();
            System.out.println("around " + joinPoint + "\tUse time : " + (end - start) + " ms with exception : " + e.getMessage());
        }
        return null;
    }

    //注意：要在这个方法中获取返回值，@Around 方法必须有返回值。
    @AfterReturning(pointcut = "aspect()", returning = "returnVal")
    public void afterReturn(JoinPoint joinPoint, Object returnVal) {  //返回值为 void 的也会被调用。
        System.out.println("afterReturn " + joinPoint + "\t" + returnVal);
    }

    //使用 @Around 后，@AfterThrowing 会不起作用。其实也没必要用 @AfterThrowing 了，应为可以直接在 @Around 的方法中，try{}catche(Throwable e){} 来捕捉异常了。
    @AfterThrowing(pointcut = "aspect()", throwing = "ex")
    public void afterThrow(JoinPoint joinPoint, Throwable ex) {
        System.out.println("afterThrow " + "\t" + ex.getMessage());
    }
}
